#include "bsp_config.h"

/**
 *  第一步: 算出临界增益KC  及 振荡周期TC
 *  KC=(4*d)/(3.14*A)   ;d=5(输出幅值)  A=记录的温度最高值与最低值的0.5倍  即:(T_Hight-T_Low)*0.5
 *  KC=(4*5)/(3.14*(T_Hight-T_Low)*0.5)=40/(3.14*(T_Hight-T_Low))=12.738/(T_Hight-T_Low)
 *  KC=12.74/(T_Hight-T_Low)
 *  TC=2*(TIME_Hight-TIME_Low)  ;2*(高点温度对应时间-低点温度对应时间)
 *  TC=2*(TIME_Hight-TIME_Low) 
 * 
 *  第二部: 算出具体的 比例系数  积分秒数   微分秒数   //系数  秒数 ???
 *  Proportion=0.6*KC
 *  I_Value=0.5*TC
 *  D_Value=0.125*TC
 * 
 *  第三部: 算出具体的 比例带 积分系数  微分系数
 *  P_Value = (1/Proportion)*100
 *  Integral= Proportion/I_Value = (0.6*KC)/(0.5*TC)
 *  Derivative=Proportion*D_Value= (0.6*KC)/(0.125*TC)
 * 
 *  第四部: 显示用的3个变量的值
 *  P_Value = (1/Proportion)*100  ;百分比
 *  I_Value=0.5*TC                ;秒
 *  D_Value=0.125*TC              ;秒
 *  
 *  第五步: 计算用的3个变量的值     我们会用
 *  Proportion=0.6*KC
 *  Integral=Proportion/I_Value=(0.6*KC)/(0.5*TC)
 *  Derivative=Proportion*D_Value=(0.6*KC)*(0.125*TC)
 */

PID_Typedef sPID;           //PID结构体变量
float Max_Value_Error = 3;  //最大偏差
float Min_Value_Error = -3; //最小偏差   偏差在3以内时,才进行PID控制 否则全功率输出
float SetValue = 30;        //设定温度值 这里初始化是没有用的
float P_Value = 0.0;        //比例带 比如56.3代表56.3%  0.0--200.0
uint16_t I_Value = 180;     //积分时间   秒 0-3600
uint16_t D_Value = 40;      //微分时间   秒 0-900
// uint8_t PID_Tune_Flag = 0;              //初始为0:即PID阶段   1:为自整定过程
uint16_t PID_Self_Time_Sec = 0;         //记录自整定的时间
uint8_t Zero_Across_Counter = 0;        //过零计数
uint8_t PID_Self_First_Status_Flag = 0; //搞什么得
uint16_t Hot_Ack_Counter = 0;           //这玩意儿相当于一个指示器
uint16_t Cool_Ack_Counter = 0;          //这玩意儿相当于一个指示器
uint16_t Once_Add_1_Flag = 0;           //这玩意儿相当于一个指示器
float T_High = 0.0;
float T_Low = 100.0; //温度
uint16_t Time_High = 0;
uint16_t Time_Low = 0;
uint16_t K_PID_Self_Counter = 0;
float PID_Self_Calc_Buffer[4];
uint8_t Enable_Calc_Min_Max_Flag = 0;
float Max_Temp = 0.0;
float Min_Temp = 0.0;
float Sum_Temp = 0.0;
float Aver_Temp = 0.0;
float KC = 20.0;   //临界比例系数   初始默认值
uint32_t TC = 600; //振荡周期       初始默认值
uint8_t K_Max_Min = 0;

/**
  * @brief  PID参数初始化,主要是给相关参数赋初始值
  * @param  None
  * @retval None
  */
void PID_Init(void)
{
  Zero_Across_Counter = 0;       //过零点计数
  PID_Self_Time_Sec = 0;         //记录整定时间
  PID_Self_Calc_Buffer[0] = 0.0; //整定用缓冲数组
  PID_Self_Calc_Buffer[1] = 0.0;
  PID_Self_Calc_Buffer[2] = 0.0;
  PID_Self_Calc_Buffer[3] = 0.0;
  K_PID_Self_Counter = 0; //普通变量相当于i
  Enable_Calc_Min_Max_Flag = 0;
  Max_Temp = 0.0;
  Min_Temp = 500.0;
  Sum_Temp = 0.0;
  Aver_Temp = 0.0;
  T_High = 0.0;
  T_Low = 500.0; //很有必要
  Time_High = 0.0;
  Time_Low = 0.0;
  // PID_Tune_Flag = ConfigParam.PID_Param.pid_param_data.Self_Tuning_Flag; //上电开始自整定
  memset(&sPID, 0, sizeof(PID_Typedef));
  sPID.SetPoint = ConfigParam.PID_Param.pid_param_data.SetPoint; //目标温度70
  //sPID.SetPoint = 170;
  sPID.Max = PID_PWM_PERIOD;
  sPID.Sta = 0;
  sPID.Proportion = ConfigParam.PID_Param.pid_param_data.Proportion; //72.085129
  sPID.Integral = ConfigParam.PID_Param.pid_param_data.Integral;     //2.363447
  sPID.Derivative = ConfigParam.PID_Param.pid_param_data.Derivative; //549.649109
  Max_Value_Error = 3;                                               //最大偏差
  Min_Value_Error = -3;                                              //最小偏差   偏差在3以内时,才进行PID控制 否则全功率输出
  P_Value = 0.0;                                                     //比例带 比如56.3代表56.3%  0.0--200.0
  I_Value = 180;                                                     //积分时间   秒 0-3600
  D_Value = 40;                                                      //微分时间   秒 0-900
  PID_Self_First_Status_Flag = 0;                                    //搞什么得
  Hot_Ack_Counter = 0;                                               //这玩意儿相当于一个指示器
  Cool_Ack_Counter = 0;                                              //这玩意儿相当于一个指示器
  Once_Add_1_Flag = 0;                                               //这玩意儿相当于一个指示器
  KC = 20.0;                                                         //临界比例系数   初始默认值
  TC = 600;                                                          //振荡周期       初始默认值
  K_Max_Min = 0;
}

/**
 * 实践联系理论
 * 理论联系实践     所以明年好好学习学习理论
 */

/**
  * @brief  PID计算
  * @param  pp:指向PID结构体
  * @param  NextPoint: 当前实际温度
  * @retval 相应输出(0-2000)
  */
static float PID_Calculate(PID_Typedef *pp, float NextPoint)
{
  float dError, Error;

  Error = pp->SetPoint - NextPoint; //设定温度>当前温度(这里只讨论正比例控制)

  if ((Error < Max_Value_Error) && (Error > Min_Value_Error))
  { //误差合规
    if ((pp->PIDResponse > 0) && (pp->PIDResponse < (pp->Max - pp->Sta)))
    {                        //输出相应大小符合要求
      pp->SumError += Error; //积分  累积误差
    }
    pp->LastError = Error;                   //当前误差变为上一次的误差
    dError = pp->LastError - pp->PreviError; //当前微分
    pp->PreviError = pp->LastError;          //   和这个对应 pp->LastError=Error;
    return (
        pp->Proportion * Error        //比例项
        + pp->Integral * pp->SumError //积分项
        + pp->Derivative * dError     //微分项
    );
  }
  else if (Error >= Max_Value_Error)
  {
    return (pp->Max - pp->Sta);
  }
  else
  {
    return 0;
  }
}

/**
  * @brief  PID自整定及控制输出 调用间隔得大于等于1s(因为一个PID PWM周期就是1s)
  * @param  CurrentValue:当前温度
  * @retval None
  */
static void PID_Tuning(float CurrentValue)
{
  static uint8_t mark = 0;
  portMUX_TYPE PID_lock = portMUX_INITIALIZER_UNLOCKED;

  taskENTER_CRITICAL(&PID_lock);

  SetValue = sPID.SetPoint; //目标温度
  if (ConfigParam.PID_Param.pid_param_data.Self_Tuning_Flag == 1)
  // if (PID_Tune_Flag == 1)
  { //自整定阶段,完毕之后转成PID控制
    //自整定OK后,自动转为PID阶段
    //自整定失败的情况下 让基本参数恢复默认值
    PID_Self_Time_Sec++;         //此函数1s调用一次,故600次即为600s=10min
    if (PID_Self_Time_Sec > 600) //
    {
      //printf("\r\n自整定失败！\r\n");
      PID_Self_Time_Sec = 0;
      ConfigParam.PID_Param.pid_param_data.Self_Tuning_Flag = 0; //即退出自整定过程,同时恢复默认的值,进入PID阶段  这样写对吗???
      // PID_Tune_Flag = 0;
      KC = 20;  //临界比例系数   初始默认值
      TC = 600; //振荡周期       初始默认值
    }

    if ((PID_Self_First_Status_Flag == 1) || (PID_Self_First_Status_Flag == 0))
    { //0:设定温度低于当前温度     1:设定温度高于或者等于当前温度启动加热
      //上面的意思就是管他三七二十一 上来就加热
      if (SetValue >= CurrentValue)
      { //当前温度小于设定温度  启动加热
        Cool_Ack_Counter = 0;
        Hot_Ack_Counter++;
        if (Hot_Ack_Counter > 3)
        { //连续3秒都是一样的结果,说明确定SetValue>=CurrentValue
          //这里只考虑了正比例控制
          sPID.PIDResponse = sPID.Max - sPID.Sta; //最大输出
          if (Once_Add_1_Flag == 0)
          { //第一次进行Hot_Ack_Counter++操作
            Once_Add_1_Flag = 1;
            Zero_Across_Counter++; //这玩意儿不能乱加(隔一个异号符)
            if (Zero_Across_Counter == 3)
            {
              Time_Low = PID_Self_Time_Sec - 3; //此时的时间不是最低温度对应的时间(???)
            }
          }
        }
      }
      else
      { //当前温度大于设定温度 停止加热
        Hot_Ack_Counter = 0;
        Cool_Ack_Counter++;
        if (Cool_Ack_Counter > 3)
        { //连续3秒都是一样的结果,说明确定SetValue<CurrentValue
          //这里只考虑了正比例控制
          sPID.PIDResponse = 0; //正比例控制嘛..让它自然凉
          if (Once_Add_1_Flag == 1)
          {
            Once_Add_1_Flag = 0; //什么时候这个东东才能变正(隔一个相异的情况)
            Zero_Across_Counter++;
            if (Zero_Across_Counter == 3)
            {
              Time_Low = PID_Self_Time_Sec - 3; //此时的时间不是最低温度对应的时间(???)
            }
          }
        }
      }
      if ((Zero_Across_Counter == 3) || (Zero_Across_Counter == 4))
      { //最低温出现在Zero_Across_Counter=3的阶段
        //最高温出现在Zero_Across_Counter=4的阶段
        PID_Self_Calc_Buffer[K_PID_Self_Counter] = CurrentValue; //记录当前温度
        K_PID_Self_Counter++;
        if (K_PID_Self_Counter > 3)
        { //只要有4个值,就可以计算了 后面来的值覆盖了前面的值
          K_PID_Self_Counter = 0;
          Enable_Calc_Min_Max_Flag = 1;
        }
        if (Enable_Calc_Min_Max_Flag == 1)
        { //只要有4个值,就可以计算了 后面来的值覆盖了前面的值
          //去掉最小值,最大值 取剩下2个值的平均值
          Sum_Temp = 0.0;   //清零
          Min_Temp = 700.0; //里面存最小值(初始化500很有必要)
          Max_Temp = 0.0;   //存最大值
          for (K_Max_Min = 0; K_Max_Min < 4; K_Max_Min++)
          {
            if (PID_Self_Calc_Buffer[K_Max_Min] <= Min_Temp)
            { //
              Min_Temp = PID_Self_Calc_Buffer[K_Max_Min];
            }
            if (PID_Self_Calc_Buffer[K_Max_Min] >= Max_Temp)
            {
              Max_Temp = PID_Self_Calc_Buffer[K_Max_Min];
            } //标准写法
            Sum_Temp = Sum_Temp + PID_Self_Calc_Buffer[K_Max_Min];
          }
          Sum_Temp = Sum_Temp - Min_Temp - Max_Temp; //这个是去除了最大和最小值然后再求和

          if (PID_Self_First_Status_Flag == 1)
          { //PID_Self_First_Status_Flag=1 时 最低温度出现在3阶段
            //PID_Self_First_Status_Flag=0 时 最低温度出现在4阶段
            if (Zero_Across_Counter == 3)
            { //最低温度
              Aver_Temp = Sum_Temp / 2.0;
              if (Aver_Temp <= T_Low)
              {
                T_Low = Aver_Temp; //T_Low里面存储的始终是最低温
              }
            }
            else if (Zero_Across_Counter == 4)
            { //最高温度
              Aver_Temp = Sum_Temp / 2.0;
              if (Aver_Temp >= T_High)
              {                     //
                T_High = Aver_Temp; //T_High里面存储的始终是最高温
              }
            }
          }
          else if (PID_Self_First_Status_Flag == 0)
          {
            if (Zero_Across_Counter == 4)
            { //最低温度
              Aver_Temp = Sum_Temp / 2.0;
              if (Aver_Temp <= T_Low)
              {
                T_Low = Aver_Temp; //T_Low里面存储的始终是最低温
              }
            }
            else if (Zero_Across_Counter == 3)
            { //最高温度
              Aver_Temp = Sum_Temp / 2.0;
              if (Aver_Temp >= T_High)
              {
                T_High = Aver_Temp; //T_High里面存储的始终是最高温
              }
            }
          }
        }
      }
      else if (Zero_Across_Counter == 5)
      { //4次过0 则说明出现了振荡 整定成功
        Zero_Across_Counter = 0;
        // PID_Tune_Flag = 0;
        ConfigParam.PID_Param.pid_param_data.Self_Tuning_Flag = 0; //进入PID阶段
        Time_High = PID_Self_Time_Sec - 3;                         //此时的时间不是最高温度对应的时间
        //计算T_High T_Low  Time_High  Time_Low 这4个值
        //根据以上4个值  KC与TC 的值便会计算出来
        //KC=12.7/(T_High-T_Low)
        //TC=2*(Time_High-Time_Low)
        KC = 12.7 * (T_High - T_Low);
        TC = 1 * (Time_High - Time_Low); //如果记录了最低温度与最高温度对应的时间,那么沿用这个公式:TC=2*(Time_High-Time_Low)
        if (KC > 1666.0)
        {
          KC = 1666.0; //对应 比例带为 0.1%
        }
        else if (KC < 0.5)
        {
          KC = 0.5; //对应 比例带为 200.0%
        }
        if (TC > 7200)
        {
          TC = 7200;
        }
        else if (TC < 8)
        {
          TC = 8;
        }                      //其实你心中已经有了答案???
        PID_Self_Time_Sec = 0; //归零
        // sPID.Proportion = 0.6 * KC * sPID.Max / 100;              //???
        // sPID.Integral = (0.6 * KC) / (0.5 * TC) * sPID.Max / 100; //sPID.Max/100  ??
        // sPID.Derivative = (0.6 * KC) * (0.125 * TC) * sPID.Max / 100;
        ConfigParam.PID_Param.pid_param_data.Proportion = sPID.Proportion = 0.6 * KC * sPID.Max / 100;            //???
        ConfigParam.PID_Param.pid_param_data.Integral = sPID.Integral = (0.6 * KC) / (0.5 * TC) * sPID.Max / 100; //sPID.Max/100  ??
        ConfigParam.PID_Param.pid_param_data.Derivative = sPID.Derivative = (0.6 * KC) * (0.125 * TC) * sPID.Max / 100;
        //同时将参数存起来
        write_config_param(&ConfigParam.PID_Param.pid_param_data.SetPoint, ConfigParam.PID_Param.pC, ConfigParam.PID_Param.length);
        mark = 1;
        //这里可以选择将P I D三个参数存储起来 供下次使用  P:72.085129 I:2.363447 D:549.649109
      }
    }
  }
  // else if (PID_Tune_Flag == 0)
  else if (ConfigParam.PID_Param.pid_param_data.Self_Tuning_Flag == 0)
  { //PID阶段,默认开机进入此阶段
    sPID.PIDResponse = PID_Calculate(&sPID, CurrentValue);
    if (sPID.PIDResponse >= sPID.Max) //下面做一下限位
    {
      sPID.PIDResponse = sPID.Max;
    }
    else if (sPID.PIDResponse < 0)
    {
      sPID.PIDResponse = 0;
    }
  }
  taskEXIT_CRITICAL(&PID_lock);
  if (mark == 1)
  {
    mark = 0; //仅存储一次
    printf("\r\nP:%f I:%f D:%f\r\n", sPID.Proportion, sPID.Integral, sPID.Derivative);
    printf("参数已经存储了!\n");
  }
}

uint8_t PID_Run_Flag = 0; //用来指示PID是否工作

/**
 *  @brief PID加热线程   每隔1000ms调用一次
 *  @param param :void 类型的指针
 *  @brief 无  
 */
void Heat_Pid_Driver_Task(void *param)
{
  while (1)
  {
    if (PID_Run_Flag == 1)
    {
      PID_Tuning(GetTemperature());
      printf("\r\n%f\r\n", sPID.PIDResponse);
      SetPIDPWMPulseWidth(sPID.PIDResponse); //设置
    }
    else if (PID_Run_Flag == 0)
    {
      SetPIDPWMPulseWidth(0);
      GetTemperature();
    }
    vTaskDelay(1000 / portTICK_PERIOD_MS); //每过20ms扫描一次按键
  }
}
